iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0

Summary of my #laser12 lectures in less than 144 characters: LINQ == monads, async await == comonands. As simple as that.
—- Erik Meijer from twitter

越來越覺得自己寫不出什麼東西,今天讓我來划水一下。接續前一篇的結果,不知道大家是否覺得很困惑,至少對我來說是這樣,只不過是寫了一個剛剛好跟Linq同名的擴充方法,為什麼就可以直接套用Query-Expression呢?這真是太莫名其妙了!!!!

查詢運算式模式

其實這完全不違反微軟的規格,文件上有說明,只要符合以下語意的方法,就可以為任意的型別支援查詢運算式

class C
{
    public C<T> Cast<T>();
}

class C<T> : C
{
    public C<T> Where(Func<T,bool> predicate);

    public C<U> Select<U>(Func<T,U> selector);

    public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
        Func<T,U,V> resultSelector);
		
		// ...
}

// 因為太長了,完整的部份放到文章最後

這裡面最大的重點在於C<T>這個型別,可是視為兩個泛型組成,分別是代表容器的C與內容物的T,而我們熟悉的Linq就是以IEnumerable去實做C,只要符合容器與內容物的組合,就可以經過加工使用查詢運算式!

LINQ == monads

容器與內容物的組合,是不是好像似曾相識?就是一直以來提到的monad阿!不要懷疑,就連Linq的開發者Erik Meijer都直接告訴我們Linq就是monads了,另外Erik Meijer也說了

every Linq function could actually be implemented by SelectMany

其實一開始Linq的設計就是針對FP的實現,並且留下了很大的彈性讓使用者可以擴充。

舉例來說,在使用webAPI時,我們經常會設計一個型別去承接回傳的資料:

public class Response<T>
{
    public string Status { get; set; }
    public T Data { get; set; }
    public string Error { get; set; }
}

注意看這個型別,就滿足了C<T>的特徵,也就是說我可以幫它寫擴充方法

static class ResponseExtension
{
    public static R Match<T, R>(this Response<T> source, Func<T, R> f)
        => source switch
        {
            { Status: "Success", Data: T d } => f(d),
            { Status: "Fail", Error: var e } => throw new Exception(e)
        };
}

如此一來就可以使用到各種Linq的好處,是不是很神奇呢?

小結

當初看到這種Query-Expression的用法讓我驚嘆不已,然而找到解答時更是大吃一驚,Query-Expression的設計真的是醍醐灌頂,其實Monad的觀念在C#中真的無處不在,回過頭來看真的有種萬物皆monad的感覺,接下來的篇幅就拿來以FP的風格來解釋型別或者介紹一些函數式風格的套件吧。

附錄

delegate R Func<T1,R>(T1 arg1);

delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);

class C
{
    public C<T> Cast<T>();
}

class C<T> : C
{
    public C<T> Where(Func<T,bool> predicate);

    public C<U> Select<U>(Func<T,U> selector);

    public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
        Func<T,U,V> resultSelector);

    public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,U,V> resultSelector);

    public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector);

    public O<T> OrderBy<K>(Func<T,K> keySelector);

    public O<T> OrderByDescending<K>(Func<T,K> keySelector);

    public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector);

    public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
        Func<T,E> elementSelector);
}

class O<T> : C<T>
{
    public O<T> ThenBy<K>(Func<T,K> keySelector);

    public O<T> ThenByDescending<K>(Func<T,K> keySelector);
}

class G<K,T> : C<T>
{
    public K Key { get; }
}

上一篇
Day21. SelectMany(2)
下一篇
Day23. Rx(1)
系列文
Functional Programming with C#30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言